/*
 * @Author: jingfuu@163.com
 * @Date: 2024-11-18 13:02:38
 * @LastEditTime: 2024-12-12 16:43:03
 * @Description: file content
 */
/*
 * @Author: jingfuu@163.com
 * @Date: 2024-11-08 09:09:25
 * @LastEditTime: 2024-11-29 10:02:43
 * @Description: file content
 */
import { Injectable, Logger } from '@nestjs/common';
import { SysUser } from '@prisma/client';
import { PrismaService } from 'src/common/prisma/PrismaService';
import { LoginUserDto } from '../dto/LoginUserDto';
import * as bcrypt from 'bcrypt';
import { JwtService } from '@nestjs/jwt';
import { CacheKey } from 'src/common/CacheKey';
import { UserSession } from 'src/common/UserSession';
import { CacheUtil } from 'src/common/CacheUtil';
import { ConfigService } from '@nestjs/config';
import { plainToInstance } from 'class-transformer';
import { UserDto } from '../dto/UserDto';
import { DeptService } from './DeptService';
import { PostService } from './PostService';
import { RoleService } from './RoleService';
import ServiceException from 'src/common/ServiceException';
import MenuService from './MenuService';
import { ClsService } from 'nestjs-cls';
import { Constants } from 'src/common/Constants';
import PageResult from 'src/common/PageResult';
import UserQueryParam from '../param/UserQueryParam';
import TreeVo from 'src/common/TreeVo';
import TreeUtil from 'src/common/TreeUtil';
import { Util } from 'src/common/Util';
import * as ExcelJS from 'exceljs';
import { RoleDto } from '../dto/RoleDto';
import { DeptDto } from '../dto/DeptDto';
import SystemConfigService from './SystemConfigService';
import { Request } from 'express';
import LoggerService from './LoggerService';

/*
 * @Author: jingfuu@163.com
 * @Date: 2024-10-06 15:11:27
 * @LastEditTime: 2024-11-08 16:53:38
 * @Description: 用户相关service
 */
@Injectable()
export class UserService {
  private readonly logger = new Logger(UserService.name);

  constructor(
    private prisma: PrismaService,
    private jwtService: JwtService,
    private configService: ConfigService,
    private deptService: DeptService,
    private postService: PostService,
    private roleService: RoleService,
    private menuService: MenuService,
    private systemConfigService: SystemConfigService,
    private cls: ClsService,
    private loggerService: LoggerService,
  ) {}

  async getById(id: number): Promise<SysUser> {
    return this.prisma.sysUser.findUnique({
      where: {
        userId: id,
      },
    });
  }

  async login(user: LoginUserDto) {
    const userLoginFailLockFlag =
      await this.systemConfigService.getUserLoginFailLockFlag();
    const userAccountLockNum = await CacheUtil.getCacheObject<number>(
      CacheKey.USER_ACCOUNT_LOCK + user.username,
    );
    const userLoginFailLockCount =
      await this.systemConfigService.getUserLoginFailLockCount();
    const userLoginFailLockTime =
      await this.systemConfigService.getUserLoginFailLockTime();
    // 1.校验验证码
    if (await this.systemConfigService.getUserCaptchaEnabled()) {
      const code = user.code;
      if (!code || !user.uuid) throw new ServiceException('验证码不能为空');
      const verifyCode = await CacheUtil.getCacheString(
        CacheKey.USER_CAPTCHA + user.uuid,
      );
      if (!verifyCode) throw new ServiceException('验证码已失效');
      if (verifyCode.toUpperCase() !== user.code.toUpperCase()) {
        throw new ServiceException('验证码错误');
      }
    }
    if (userLoginFailLockFlag && userAccountLockNum > userLoginFailLockCount) {
      throw new ServiceException(
        '输入密码错误已超过' +
          userLoginFailLockCount +
          '次,请在' +
          userLoginFailLockTime +
          '分钟后再试',
      );
    }
    // 2.根据用户名查询数据库
    const sysUser = await this.prisma.sysUser.findUnique({
      where: {
        userName: user.username,
      },
    });
    if (!sysUser) {
      throw new ServiceException('用户不存在');
    }
    // 3.用户校验
    // 3.1.校验密码
    const checked = bcrypt.compareSync(user.password, sysUser.password);
    if (!checked) {
      if (userLoginFailLockFlag)
        CacheUtil.increment(CacheKey.USER_ACCOUNT_LOCK + user.username);
      throw new ServiceException('密码错误');
    }
    // 3.1.校验账号状态
    if (sysUser.status == '1') {
      throw new ServiceException('该账号已冻结！');
    }
    // 4.生成token，存放redis中，同时生成用户session
    const payload = { sub: sysUser.userId, userName: sysUser.userName };
    const token = await this.jwtService.signAsync(payload);
    CacheUtil.setCacheObject(
      CacheKey.USER_TOKEN + token,
      sysUser.userId,
      3600 * (await this.systemConfigService.getUserSessionEffectiveTime()),
    );
    const userDto = plainToInstance(UserDto, sysUser);
    //查部门、职务
    const { dept, posts } = await this.getUserDeptAndPosts(userDto);
    userDto.posts = posts;
    userDto.dept = dept;
    const { roles, permissions } =
      await this.getUserRoleAndPermissions(userDto);
    userDto.roles = roles;
    userDto.permissions = permissions;
    let session = await CacheUtil.getCacheObject<UserSession>(
      CacheKey.USER_SESSION + sysUser.userId,
    );
    if (!session) {
      session = new UserSession();
    }
    session.user = userDto;
    session.userId = userDto.userId;
    session.permissions = permissions;
    session.roles = userDto.roles.map((role) => role.roleKey);
    session.tokenList.push(token);
    CacheUtil.setCacheObject(
      CacheKey.USER_SESSION + sysUser.userId,
      JSON.stringify(session),
      3600 * (await this.systemConfigService.getUserSessionEffectiveTime()),
    );
    return token;
  }

  async getUserRoleAndPermissions(userDto: UserDto) {
    const roles = await this.roleService
      .getByUserId(userDto.userId)
      .catch((e) => {
        this.logger.error('查询角色异常', e);
        throw new ServiceException('用户' + userDto.nickName + '查询角色异常');
      });
    const permissions = await this.menuService.getUserPermissions(
      roles.map((role) => role.roleId),
    );
    return {
      roles,
      permissions,
    };
  }

  async getUserDeptAndPosts(userDto: UserDto) {
    const [dept, posts] = await Promise.all([
      this.deptService.getNormalById(userDto.deptId).catch((e) => {
        this.logger.error('查询部门异常', e);
        throw new ServiceException('用户' + userDto.nickName + '查询部门异常');
      }),
      this.postService.getByUserId(userDto.userId).catch((e) => {
        this.logger.error('查询职位异常', e);
        throw new ServiceException('用户' + userDto.nickName + '查询职位异常');
      }),
    ]);
    return {
      dept,
      posts,
    };
  }

  async logout(req: Request) {
    const userToken = this.cls.get(Constants.USER_TOKEN);
    const userId = await CacheUtil.getCacheString(
      CacheKey.USER_TOKEN + userToken,
    );
    if (!userId) {
      return;
    }
    const userSession = await CacheUtil.getCacheObject<UserSession>(
      CacheKey.USER_SESSION + userId,
    );
    userSession.tokenList = userSession.tokenList.filter(
      (token) => token !== userToken,
    );
    await CacheUtil.delCache(CacheKey.USER_TOKEN + userToken);
    if (userSession.tokenList.length === 0) {
      await CacheUtil.delCache(CacheKey.USER_SESSION + userSession.userId);
    } else {
      await CacheUtil.setCacheObject(
        CacheKey.USER_SESSION + userSession.userId,
        JSON.stringify(userSession),
        3600 * (await this.systemConfigService.getUserSessionEffectiveTime()),
      );
    }
    const openLogininforFlag =
      await this.systemConfigService.getUserOpenLogininforFlag();
    if (openLogininforFlag)
      this.loggerService.loginInfoAdd(
        userSession.user.userName,
        req,
        '0',
        '退出登录成功',
      );
  }

  /**
   * 获取用户所有路由
   * @param userId 用户id
   */
  async getRouters(roleIds: number[]): Promise<any> {
    return await this.menuService.getUserRouters(roleIds);
  }

  async list(param: UserQueryParam): Promise<PageResult<UserDto>> {
    const skip = param.pageNum != 0 ? (param.pageNum - 1) * param.pageSize : 0;
    const whereClause: any = this.generateUserQueryCondition(param);
    const [total, users] = await this.prisma.$transaction([
      this.prisma.sysUser.count({ where: whereClause }),
      this.prisma.sysUser.findMany({
        skip,
        take: param.pageSize,
        orderBy: {
          createTime: 'desc',
        },
        where: whereClause,
      }),
    ]);
    const rows: UserDto[] = users.map((user) => plainToInstance(UserDto, user));
    for (const row of rows) {
      if (!row.deptId) continue;
      const dept = await this.prisma.sysDept.findUnique({
        where: {
          deptId: row.deptId,
        },
      });
      row.dept = plainToInstance(DeptDto, dept);
    }
    return {
      total,
      rows,
    };
  }

  private generateUserQueryCondition(param: UserQueryParam) {
    const whereClause: any = {};

    if (param.userName) {
      whereClause.userName = {
        contains: param.userName,
      };
    }

    if (param.phonenumber) {
      whereClause.phonenumber = {
        contains: param.phonenumber,
      };
    }

    if (param.deptId) {
      whereClause.deptId = param.deptId;
    }

    if (param.params?.beginTime) {
      const beginTime = Util.convertUTCToLocalTime(
        new Date(`${param.params.beginTime}T10:00:00`),
      );
      if (!isNaN(beginTime.getTime())) {
        // Check if the date is valid
        whereClause.createTime = {
          ...whereClause.createTime,
          gte: beginTime,
        };
      }
    }
    if (param.params?.endTime) {
      const endTime = Util.convertUTCToLocalTime(
        new Date(`${param.params.endTime}T23:59:59`),
      );
      if (!isNaN(endTime.getTime())) {
        // Check if the date is valid
        whereClause.createTime = {
          ...whereClause.createTime,
          lte: endTime,
        };
      }
    }

    if (param.status !== undefined) {
      whereClause.status = param.status;
    }
    return whereClause;
  }

  async deptTree(): Promise<TreeVo[]> {
    const depts = await this.prisma.sysDept.findMany();
    const vos = depts.map((dept) => {
      const vo = new TreeVo();
      vo.id = dept.deptId;
      vo.pid = dept.parentId;
      vo.label = dept.deptName;
      vo.sort = dept.orderNum;
      return vo;
    });
    return TreeUtil.buildTree<TreeVo>(vos);
  }

  async deleteUser(id: number) {
    await this.prisma.sysUser.delete({
      where: {
        userId: id,
      },
    });
  }

  async getDetail(id: number): Promise<any> {
    const roleIds = await this.roleService.getIdsByUserId(id);
    let allRole = await this.roleService.getAllRole();
    allRole = allRole.filter((role) => role.roleId != 1);
    const postIds = await this.postService.getIdsByUserId(id);
    const allPost = await this.postService.getAllPost();

    return {
      posts: allPost,
      postIds,
      roles: allRole,
      roleIds,
      code: Constants.SUCCESS,
      data: plainToInstance(UserDto, await this.getById(id)),
    };
  }

  async getPostAndRole(): Promise<any> {
    return {
      posts: await this.postService.getAllPost,
      roles: await this.roleService.getAllRole(),
      code: Constants.SUCCESS,
    };
  }

  async updateUser(user: UserDto) {
    //1.更新基本信息
    await this.prisma.sysUser.update({
      where: {
        userId: user.userId,
      },
      data: {
        userName: user.userName,
        nickName: user.nickName,
        email: user.email,
        phonenumber: user.phonenumber,
        sex: user.sex,
        status: user.status,
        deptId: user.deptId,
        remark: user.remark,
        password: user.password
          ? Util.encryptPassword(user.password)
          : undefined,
      },
    });
    //2.更新岗位
    if (user.postIds)
      await this.postService.updateUserPost(user.userId, user.postIds);
    //3.更新角色
    if (user.roleIds)
      await this.roleService.updateUserRole(user.userId, user.roleIds);
  }
  async addUser(user: UserDto) {
    //1.添加用户
    await this.checkAddUser(user);
    const newUser = await this.prisma.sysUser.create({
      data: {
        userName: user.userName,
        nickName: user.nickName,
        email: user.email,
        phonenumber: user.phonenumber,
        sex: user.sex,
        password: Util.encryptPassword(user.password),
        status: user.status,
        deptId: user.deptId,
        remark: user.remark,
      },
    });
    this.logger.log('新增用户成功：' + newUser);
    //2.更新岗位
    await this.postService.updateUserPost(newUser.userId, user.postIds);
    //3.更新角色
    await this.roleService.updateUserRole(newUser.userId, user.roleIds);
  }
  async checkAddUser(user: UserDto) {
    //通过用户名查询是否存在，存在报错
    if (user.userName) {
      const existUser = await this.getByUserName(user.userName);
      if (existUser) throw new ServiceException('用户名已存在');
    }
  }
  async getByUserName(userName: string) {
    //通过用户名查询用户
    return this.prisma.sysUser.findFirst({
      where: {
        userName,
      },
    });
  }

  async export(param: UserQueryParam): Promise<Buffer> {
    const whereClause: any = this.generateUserQueryCondition(param);
    const sysUsers = await this.prisma.sysUser.findMany({
      orderBy: {
        createTime: 'desc',
      },
      where: whereClause,
    });
    const userDtos = plainToInstance(UserDto, sysUsers);
    for (const user of userDtos) {
      if (user.deptId) user.dept = await this.deptService.getById(user.deptId);
    }
    // 设置列头
    const header = [
      { header: '用户序号', key: 'id', width: 10 },
      { header: '登录名称', key: 'username', width: 20 },
      { header: '用户名称', key: 'nickName', width: 20 },
      { header: '用户邮箱', key: 'email', width: 20 },
      { header: '手机号码', key: 'phonenumber', width: 20 },
      { header: '用户性别', key: 'sex', width: 10 },
      { header: '账号状态', key: 'status', width: 10 },
      { header: '最后登录IP', key: 'loginIp', width: 30 },
      { header: '最后登录时间', key: 'loginDate', width: 30 },
      { header: '部门名称', key: 'deptName', width: 30 },
      { header: '部门负责人', key: 'deptLeader', width: 30 },
    ];
    const users = userDtos.map((user) => {
      return {
        id: user.userId,
        username: user.userName,
        nickName: user.nickName,
        email: user.email,
        phonenumber: user.phonenumber,
        sex: user.sex == '1' ? '男' : '女',
        status: user.status == '0' ? '正常' : '停用',
        loginIp: user.loginIp,
        loginDate: user.loginDate,
        deptName: user.dept?.deptName || '',
        deptLeader: user.dept?.leader || '',
      };
    });
    return Util.excelExport(users, header);
  }

  async exportTemplate() {
    // 设置列头
    const header = [
      { header: '部门编号', key: 'deptId', width: 10 },
      { header: '登录名称', key: 'username', width: 20 },
      { header: '用户名称', key: 'nickName', width: 20 },
      { header: '用户邮箱', key: 'email', width: 20 },
      { header: '手机号码', key: 'phonenumber', width: 20 },
      { header: '用户性别', key: 'sex', width: 10 },
      { header: '账号状态', key: 'status', width: 10 },
    ];
    return Util.excelExportTemplate(header);
  }

  /**
   * excel导入用户账号
   * @param file 用户账号excel
   * @param updateSupport  是否支持更新
   */
  async importUser(file: Express.Multer.File, updateSupport: boolean) {
    // 查询出所有部门
    const deptList = await this.deptService.getAllDept();
    const deptIds = deptList.map((item) => item.deptId);
    const workbook = new ExcelJS.Workbook();
    await workbook.xlsx.load(file.buffer);
    const worksheet = workbook.getWorksheet(1); // 获取第一个工作表
    const data: UserDto[] = [];
    //默认密码，取sys_config
    const defaultPwd = await this.systemConfigService.getUserDefaultPassword();
    const defaultPassword = Util.encryptPassword(defaultPwd);
    worksheet.eachRow((row, rowNumber) => {
      if (rowNumber === 1) return; // 跳过表头
      const deptId = row.getCell(1).value as number;
      if (!deptIds.includes(deptId)) {
        throw new ServiceException(
          '第' + rowNumber + '行数据，导入部门编号不存在',
        );
      }
      const userName = row.getCell(2).value + '';
      if (!userName) {
        throw new ServiceException('第' + rowNumber + '行数据，用户名不能为空');
      }
      const nickName = row.getCell(3).value + '';
      if (!nickName) {
        throw new ServiceException(
          '第' + rowNumber + '行数据，用户昵称不能为空',
        );
      }
      const email = row.getCell(4).value as string;
      if (!email) {
        throw new ServiceException(
          '第' + rowNumber + '行数据，用户邮箱不能为空',
        );
      }
      const phonenumber = row.getCell(5).value + '';
      if (!phonenumber) {
        throw new ServiceException(
          '第' + rowNumber + '行数据，手机号码不能为空',
        );
      }
      let sex = row.getCell(6).value as string;
      if (!sex || (sex !== '男' && sex !== '女')) {
        throw new ServiceException('第' + rowNumber + '行数据，性别有误');
      } else if (sex === '男') {
        sex = '1';
      } else if (sex === '女') {
        sex = '2';
      }
      let status = row.getCell(7).value as string;
      if (!status || (status !== '正常' && status !== '停用')) {
        throw new ServiceException('第' + rowNumber + '行数据，状态有误');
      } else if (status === '停用') {
        status = '1';
      } else {
        status = '0';
      }
      const rowData = plainToInstance(UserDto, {
        deptId: deptId,
        userName,
        nickName,
        email,
        phonenumber,
        sex,
        status,
        password: defaultPassword,
      });
      data.push(rowData);
    });
    for (const user of data) {
      const userName = user.userName;
      const existUser = await this.prisma.sysUser.findFirst({
        where: { userName },
      });
      if (existUser) {
        if (!updateSupport)
          throw new ServiceException('用户' + userName + '已存在');
        //更新用户
        await this.prisma.sysUser.update({
          where: { userName },
          data: {
            nickName: user.nickName,
            email: user.email,
            phonenumber: user.phonenumber,
            sex: user.sex,
            status: user.status,
          },
        });
      } else {
        await this.checkAddUser(user);
        await this.prisma.sysUser.create({ data: user });
      }
    }
  }

  async authRole(id: number): Promise<any> {
    const user = await this.getById(id);
    const roles = await this.roleService.getAllRole();
    const roleDtos = plainToInstance(RoleDto, roles);
    for (const role of roleDtos) {
      const checkedRole = await this.prisma.sysUserRole.findMany({
        where: {
          userId: id,
        },
        select: {
          roleId: true,
        },
      });
      const checkedRoled = checkedRole.map((role) => role.roleId);
      role.flag = checkedRoled.includes(role.roleId);
    }
    return {
      user,
      roles: roleDtos,
      code: Constants.SUCCESS,
    };
  }

  async updateUserPwd(
    userId: number,
    oldPassword: string,
    newPassword: string,
  ) {
    if (!oldPassword) {
      throw new ServiceException('请输入旧密码');
    }
    if (!newPassword) {
      throw new ServiceException('请输入新密码');
    }
    const sysUser = await this.getById(userId);
    if (!bcrypt.compareSync(oldPassword, sysUser.password)) {
      throw new ServiceException('旧密码错误');
    }
    if (oldPassword === newPassword) {
      throw new ServiceException('新密码不能与旧密码相同');
    }
    await this.prisma.sysUser.update({
      where: { userId },
      data: {
        password: await bcrypt.hash(newPassword, 10),
      },
    });
  }
}
